#!/bin/env python
"""Simulates microphones and speakers.

Copyright (C) Sarah Mount.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA


Sounds are sent by sounders and received by microphones.
"""

import Dingo.API as api
import ranger
import copy, os, shelve, threading, wx
from Queue import Queue

__author__ = "Sarah Mount"

class Beep(object):
    """Represents a Beep. Sent by Sounders and received by Microphones.
    """
    
    #Reachability kludge.
    radius = 75
    """@cvar: Reachability kludge! in pixels / cm"""
    #End Reachability kludge.
    def __init__(self, sensor_id, time_sent, freq=10, vol=2, time_recv=None, tof=None):
        """Create a beep object.

        @type sensor_id: str
        @param sensor_id:  The emitting sensor
        @type time_sent: float
        @param time_sent: When the sound was made
        @type freq: float
        @param freq: Frequency of the sound in kHz
        @type vol: float
        @param vol: The sound volume in dB
        @type time_recv: float
        @param time_recv: Kludge to fake time received
        @type tof: float
        @param tof:  Time of flight Seconds
        """
        self.sender = sensor_id        # Sender kludge!
        self.frequency = freq          # kHz
        self.volume = vol              # dB 
        self.time_sent = time_sent     # sec
        self.time_received = time_recv # sec
        self.time_of_flight = tof      # sec
        return
                
                
    def __repr__(self):
        """Return a formatted string representation of a sound"""
        return " ".join(["Beep! from", self.sender, "sent at", str(self.time_sent), "received at", str(self.time_received), "Time of flight:", str(self.time_of_flight)+"s", "Frequency:", str(self.frequency)+"kHz", "Volume:", str(self.volume)+"dB"])

class SoundSensor(object):
    """ Defines a sensor capable of sending and receiving sounds.
    
    Sensors using sound must inherit from L{Dingo.sensor.Sensor} I{and}
    L{Dingo.sound.SoundSensor}.
    """
    def __init__(self, id, path, ranger=ranger.NaiveSoundRanger()):
        self.microphone_name = path + id + '-sounder.db'
        self.microphone_lock = threading.Lock()
        self.sound_q = Queue() # Queue holding sound data
        self.ranger = ranger
        self.current_key = 1   # Keep track of heard / unheard sounds
        cwd = os.getcwd()
        # Oddly, cwd() doesn't always return the correct value.
        # So, here we check that we're in the right place.
        file = os.sep + 'sounds' + os.sep + 'beep.wav'
        if (cwd.split(os.sep))[-1] == 'Dingo':
            self.beep_file = cwd + file
        else :
            self.beep_file = cwd + os.sep + 'Dingo' + file
        self.beep = wx.Sound(self.beep_file)
        # Check that path exists.
        if not os.access(path, os.F_OK):
            os.mkdir(path)
        # Check that microphone file exists -- avoid race conditions
        # if a Microphone reads from the microphone before a Sounder
        # writes to it.
        if not os.access(self.microphone_name, os.F_OK):
            # No touch() function in Python?
            self._open_microphone()
        self._close_microphone()
        return
        
    def make_sound(self, time_sent=None):
        reachable = self.ether.reachable_sensors(self.id, Beep.radius)
        try:
            #pass
                self.beep.Play(wx.SOUND_SYNC) # Comment out to preserve sanity
        except:
            pass
        if time_sent == None:
            sound = Beep(self.id, self.clock())
        else:
            sound= Beep(self.id, time_sent)
            for r_id in reachable:
                r = self.ether.get_sensor(r_id)
            snd_cp = copy.copy(sound)
            (s_x, s_y) = (self.node.GetX(), self.node.GetY())
            (r_x, r_y) = (r.node.GetX(), r.node.GetY())
            tof = self.ranger.get_tof((s_x, s_y), (r_x, r_y))
            vol = self.ranger.get_new_volume((s_x, s_y), (r_x, r_y), snd_cp.volume)
            snd_cp.time_received = snd_cp.time_sent + tof
            snd_cp.time_of_flight = tof
            snd_cp.volume = vol
            r.write_to_microphone(snd_cp)
        return
    def write_to_microphone(self, sound):
        self._open_microphone()
        # Don't ever use self.current_key for this.
        key = len(self.microphone.items()) + 1
        self.microphone[str(key)] = sound
        self._close_microphone()
        return
    
    def read_from_microphone(self):
        self._open_microphone()
        while True:
            try:
                self.sound_q.put(self.microphone[str(self.current_key)])
                self.current_key = self.current_key + 1
            except KeyError:
                self._close_microphone()
                return
        # Shouldn't ever reach this, but just in case...
        self._close_microphone()
        return
            
    def _open_microphone(self):
        self.microphone_lock.acquire()
        self.microphone = shelve.open(self.microphone_name)
        return
    
    def _close_microphone(self):
        self.microphone.close()
        self.microphone_lock.release()
        return

class SoundTask(api.Task):
    """Superclass for Microphone and Sounder.
    Initialises locks, stores filenames, etc.
    """
    def __init__(self, priority, repetition, sensor):
        api.Task.__init__(self, priority, repetition, sensor)
        return
    
    def task(self):
        """Overwritten in Sounder and Microphone."""
        return

class Sounder(SoundTask):
    """Simple sounding task for sensors.
    All sensors have a file named $SENSOR_ID-sounder.db in which
    sound data is placed.
    """
    def __init__(self, priority, repetition, sensor):
        SoundTask.__init__(self, priority, repetition, sensor)
        return
    
    def task(self):
        """Send a sound to all sensors within hearing range."""
        self.sensor.make_sound()
        return              

class Microphone(SoundTask):
    """Simple microphone task for sensors.
    All sensors have a file named $SENSOR_ID-sounder.db in which sound
    data is placed.
    """
        
    def __init__(self, priority, repetition, sensor):
        SoundTask.__init__(self, priority, repetition, sensor)
        return
    
    def task(self):
        self.sensor.read_from_microphone()
        return
